/**
 * This file is part of Idegen, the application to create IDE project files.
 *
 * @copyright Copyright (c) 2011-2012, Walter Tamboer http://waltertamboer.nl
 * @file Idegen/Generator/VS2010Generator.cpp
 */

#include "Idegen/PrecompiledHeader.hpp"
#include "Idegen/Generator/IGenerator.hpp"
#include "Idegen/Generator/VisualStudioGenerator.hpp"
#include "Idegen/Generator/VS2010Generator.hpp"
#include "Idegen/Idegen/IdegenGenerator.hpp"
#include "Idegen/Idegen/IdegenProject.hpp"
#include "Idegen/IO/File.hpp"
#include "Idegen/IO/FileSystem.hpp"
#include "Idegen/Idegen/IdegenProjectFile.hpp"
#include "Idegen/Idegen/IdegenProjectFilter.hpp"
#include "Idegen/Idegen/IdegenPrecompiledHeader.hpp"
#include "Idegen/Idegen/IdegenSourceSet.hpp"
#include "Idegen/Idegen/IdegenProjectConfig.hpp"
#include "Idegen/Idegen/IdegenProjectType.hpp"
#include "Idegen/Idegen/Charset.hpp"
#include "Idegen/Application/Settings.hpp"

namespace Idegen
{
	VS2010Generator::VS2010Generator()
	{
	}

	void VS2010Generator::GenerateProject( Settings* settings, IdegenProject* project )
	{
		GenerateProjectFilters( project );

		const IdegenProjectConfigList configs = project->GetConfigs();
		const IdegenSourceSetList sources = project->GetSources();

		// The output path of this project:
		const std::string path = m_OutputPath + '/' + project->GetName() + ".vcxproj";
		TiXmlDocument document( path.c_str() );
		
		TiXmlDeclaration* declaration = new TiXmlDeclaration( "1.0", "utf-8", "" );
		document.LinkEndChild( declaration );

		TiXmlElement* rootElement = new TiXmlElement( "Project" );
		rootElement->SetAttribute( "DefaultTargets", "Build" );
		rootElement->SetAttribute( "ToolsVersion", "4.0" );
		rootElement->SetAttribute( "xmlns", "http://schemas.microsoft.com/developer/msbuild/2003" );
		document.LinkEndChild( rootElement );
		
		TiXmlElement* itemGroup = new TiXmlElement( "ItemGroup" );
		for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
		{
			const IdegenProjectConfig* config = (*itConfig);
			itemGroup->LinkEndChild( WriteProjectConfiguration( config->GetName(), config->GetPlatform() ) );
		}
		rootElement->LinkEndChild( itemGroup );
		
		TiXmlElement* itemGroupInc = new TiXmlElement( "ItemGroup" );
		TiXmlElement* itemGroupSrc = new TiXmlElement( "ItemGroup" );
		TiXmlElement* itemGroupOth = new TiXmlElement( "ItemGroup" );

		for ( IdegenSourceSetList::const_iterator it = sources.begin(); it != sources.end(); ++it )
		{
			const ProjectFileList files = (*it)->GetFiles( true );
			for ( ProjectFileList::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt )
			{
				const IdegenProjectFile* file = *fileIt;
				const std::string relativePath = AbsoluteToRelativePath( file->TheFile.GetPath(), m_OutputPath );

				TiXmlElement* element = NULL;
				if ( file->TheFile.HasExtension(".h") || file->TheFile.HasExtension(".hpp") )
				{
					element = new TiXmlElement( "ClInclude" );
					itemGroupInc->LinkEndChild( element );
				}
				else if ( file->TheFile.HasExtension(".c") || file->TheFile.HasExtension(".cpp") || file->TheFile.HasExtension(".cxx") )
				{
					element = new TiXmlElement( "ClCompile" );
					itemGroupSrc->LinkEndChild( element );
					
					for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
					{
						const IdegenProjectConfig* config = (*itConfig);

						IdegenPrecompiledHeader* precompiledHeader = config->GetPrecompiledHeader();
						if ( precompiledHeader != NULL )
						{
							unsigned int lastMatchPos = relativePath.rfind( ConvertToSystemSlashes( precompiledHeader->File ) );
							if ( lastMatchPos != std::string::npos )
							{
								TiXmlElement* pchElement = NULL;

								std::string condition = "'$(Configuration)|$(Platform)'=='" + config->GetName() + "|" + config->GetPlatform() + "'";
							
								pchElement = new TiXmlElement( "PrecompiledHeader" );
								pchElement->SetAttribute( "Condition", condition.c_str() );
								pchElement->LinkEndChild( new TiXmlText( "Create" ) );
								element->LinkEndChild( pchElement );
							}
						}
					}
				}
				else
				{
					element = new TiXmlElement( "None" );
					itemGroupOth->LinkEndChild( element );
				}
				
				element->SetAttribute( "Include", relativePath.c_str() );
			}
		}

		if ( itemGroupInc->FirstChildElement() != NULL ) { rootElement->LinkEndChild( itemGroupInc ); }
		else { delete itemGroupInc; }

		if ( itemGroupSrc->FirstChildElement() != NULL ) { rootElement->LinkEndChild( itemGroupSrc ); }
		else { delete itemGroupSrc; }

		if ( itemGroupOth->FirstChildElement() != NULL ) { rootElement->LinkEndChild( itemGroupOth ); }
		else { delete itemGroupOth; }
		
		TiXmlElement* element = NULL;
		TiXmlElement* container = NULL;

		/**********/

		container = WriteElement( rootElement, "ItemGroup" );
		container->SetAttribute( "Label", "ProjectConfigurations" );
		
		for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
		{
			const IdegenProjectConfig* config = (*itConfig);
			
			element = WriteElement( container, "ProjectConfiguration" );
			element->SetAttribute( "Include", (config->GetName() + "|" + config->GetPlatform()).c_str() );
			
			WriteElementValue( element, "Configuration", config->GetName() );
			WriteElementValue( element, "Platform", config->GetPlatform() );
		}
		
		/**********/

		container = WriteElement( rootElement, "PropertyGroup" );
		container->SetAttribute( "Label", "Globals" );
		
		WriteElementValue( container, "ProjectGuid", "{B86D76D2-6ED3-4A9E-ACAD-8291598F3555}" );
		WriteElementValue( container, "Keyword", "Win32Proj" );
		WriteElementValue( container, "RootNamespace", project->GetName().c_str() );
		WriteElementValue( container, "ProjectName", project->GetName().c_str() );
		
		/**********/

		element = WriteElement( rootElement, "Import" );
		element->SetAttribute( "Project", "$(VCTargetsPath)\\Microsoft.Cpp.Default.props" );
		
		/**********/
		
		for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
		{
			const IdegenProjectConfig* config = (*itConfig);

			container = WriteElement( rootElement, "PropertyGroup" );
			container->SetAttribute( "Label", "Configuration" );

			std::string condition = "'$(Configuration)|$(Platform)'=='" + config->GetName() + "|" + config->GetPlatform() + "'";
			container->SetAttribute( "Condition", condition.c_str() );
		
			WriteElementValue( container, "ConfigurationType", "StaticLibrary" );
			WriteElementValue( container, "UseDebugLibraries", config->UseDebugLibraries() ? "true" : "false" );
			WriteElementValue( container, "WholeProgramOptimization", config->UseDebugLibraries() ? "false" : "true" );

			switch ( config->GetCharset() )
			{
			case CharsetMultiByte:
				WriteElementValue( container, "CharacterSet", "MultiByte" );
				break;
				
			case CharsetUnicode:
				WriteElementValue( container, "CharacterSet", "Unicode" );
				break;
				
			default:
				WriteElementValue( container, "CharacterSet", "NotSet" );
				break;
			}
		}
		
		/**********/

		element = WriteElement( rootElement, "Import" );
		element->SetAttribute( "Project", "$(VCTargetsPath)\\Microsoft.Cpp.props" );
		
		/**********/

		container = WriteElement( rootElement, "ImportGroup" );
		container->SetAttribute( "Label", "ExtensionSettings" );

		/**********/

		for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
		{
			const IdegenProjectConfig* config = (*itConfig);

			container = WriteElement( rootElement, "ImportGroup" );
			container->SetAttribute( "Label", "PropertySheets" );
			
			std::string condition = "'$(Configuration)|$(Platform)'=='" + config->GetName() + "|" + config->GetPlatform() + "'";
			container->SetAttribute( "Condition", condition.c_str() );
			
			element = WriteElement( container, "Import" );
			element->SetAttribute( "Project", "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props" );
			element->SetAttribute( "Condition", "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')" );
			element->SetAttribute( "Label", "LocalAppDataPlatform" );
		}

		/**********/
		
		element = WriteElement( rootElement, "PropertyGroup" );
		element->SetAttribute( "Label", "UserMacros" );

		/**********/
		
		for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
		{
			const IdegenProjectConfig* config = (*itConfig);

			container = WriteElement( rootElement, "PropertyGroup" );
			
			std::string condition = "'$(Configuration)|$(Platform)'=='" + config->GetName() + "|" + config->GetPlatform() + "'";
			container->SetAttribute( "Condition", condition.c_str() );

			WriteElementValue( container, "OutDir", RelativeToAbsolutePath( config->GetOutputPath(), settings->GetBasePath(), true ).c_str() );
			WriteElementValue( container, "IntDir", RelativeToAbsolutePath( config->GetTempPath(), settings->GetBasePath(), true ).c_str() );
		}

		/**********/
		
		for ( IdegenProjectConfigList::const_iterator itConfig = configs.begin(); itConfig != configs.end(); ++itConfig )
		{
			const IdegenProjectConfig* config = (*itConfig);
			const IdegenPrecompiledHeader* precompiledHeader = config->GetPrecompiledHeader();
		
			container = WriteElement( rootElement, "ItemDefinitionGroup" );
			
			std::string condition = "'$(Configuration)|$(Platform)'=='" + config->GetName() + "|" + config->GetPlatform() + "'";
			container->SetAttribute( "Condition", condition.c_str() );
			
			element = WriteElement( container, "ClCompile" );
			WriteElementValue( element, "PrecompiledHeader", precompiledHeader == NULL ? "NotUsing" : "Use" );

			switch ( config->GetWarningLevel() )
			{
			case 0:
				WriteElementValue( element, "WarningLevel", "Level0" );
				break;
			case 1:
				WriteElementValue( element, "WarningLevel", "Level1" );
				break;
			case 2:
				WriteElementValue( element, "WarningLevel", "Level2" );
				break;
			case 3:
				WriteElementValue( element, "WarningLevel", "Level3" );
				break;
			case 4:
				WriteElementValue( element, "WarningLevel", "Level4" );
				break;
			}

			WriteElementValue( element, "TreatWarningAsError", config->UseWarningsAsErrors() ? "true" : "false" );

			WriteIncludeDirectories( element, settings, config );
			WriteDefinitions( element, project, config );

			if ( config->UseDebugLibraries() )
			{
				WriteElementValue( element, "Optimization", "Disabled" );
			}
			else
			{
				WriteElementValue( element, "Optimization", "MaxSpeed" );
				WriteElementValue( element, "FunctionLevelLinking", "true" );
				WriteElementValue( element, "IntrinsicFunctions", "true" );
			}
		
			std::string precompiledHeaderPath = precompiledHeader != NULL ? precompiledHeader->Name : "";
			WriteElementValue( element, "PrecompiledHeaderFile", precompiledHeaderPath.c_str() );
		
			element = WriteElement( container, "Link" );
			WriteElementValue( element, "SubSystem", "Windows" );
			WriteElementValue( element, "GenerateDebugInformation", config->UseDebugLibraries() ? "true" : "false" );
			if ( !config->UseDebugLibraries() )
			{
				WriteElementValue( element, "EnableCOMDATFolding", "true" );
				WriteElementValue( element, "OptimizeReferences", "true" );
			}
		
			element = WriteElement( container, "Lib" );
			WriteLibraryDirectories( element, settings, config );
		}

		/**********/

		element = WriteElement( rootElement, "Import" );
		element->SetAttribute( "Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets" );

		/**********/

		element = WriteElement( rootElement, "ImportGroup" );
		element->SetAttribute( "Label", "ExtensionTargets" );
		
		document.SaveFile();
	}

	void VS2010Generator::GenerateProjectFilters( IdegenProject* project )
	{
		std::string projOutputPath = project->GetOutputPath();
		projOutputPath = projOutputPath.empty() ? m_OutputPath : m_OutputPath;

		const std::string path = projOutputPath + '/' + project->GetName() + ".vcxproj.filters";
		TiXmlDocument document( path.c_str() );
		
		TiXmlDeclaration* declaration = new TiXmlDeclaration( "1.0", "utf-8", "" );
		document.LinkEndChild( declaration );
		
		TiXmlElement* rootElement = new TiXmlElement( "Project" );
		rootElement->SetAttribute( "ToolsVersion", "4.0" );
		rootElement->SetAttribute( "xmlns", "http://schemas.microsoft.com/developer/msbuild/2003" );
		document.LinkEndChild( rootElement );
		
		GenerateProjectFiles( rootElement, project );
		GenerateProjectFilterItems( rootElement, project );
		
		document.SaveFile();
	}
	
	void VS2010Generator::GenerateProjectFiles( TiXmlElement* parent, IdegenProject* project )
	{
		TiXmlElement* incGroup = new TiXmlElement( "ItemGroup" );
		TiXmlElement* srcGroup = new TiXmlElement( "ItemGroup" );
		TiXmlElement* noneGroup = new TiXmlElement( "ItemGroup" );
		
		const IdegenSourceSetList sources = project->GetSources();
		for ( IdegenSourceSetList::const_iterator it = sources.begin(); it != sources.end(); ++it )
		{
			const ProjectFileList files = (*it)->GetFiles( true );
			for ( ProjectFileList::const_iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt )
			{
				const IdegenProjectFile* file = *fileIt;
				const std::string relativePath = AbsoluteToRelativePath( file->TheFile.GetPath(), m_OutputPath );

				TiXmlElement* element = NULL;
				if ( file->TheFile.HasExtension(".h") || file->TheFile.HasExtension(".hpp") )
				{
					element = new TiXmlElement( "ClInclude" );
					incGroup->LinkEndChild( element );
				}
				else if ( file->TheFile.HasExtension(".c") || file->TheFile.HasExtension(".cpp") )
				{
					element = new TiXmlElement( "ClCompile" );
					incGroup->LinkEndChild( element );
				}
				else
				{
					element = new TiXmlElement( "None" );
					noneGroup->LinkEndChild( element );
				}
				
				element->SetAttribute( "Include", relativePath.c_str() );

				if ( file->Filter != NULL && !file->Filter->GetName().empty() )
				{
					TiXmlElement* filterElement = new TiXmlElement( "Filter" );
					filterElement->LinkEndChild( new TiXmlText( file->Filter->GetFullName().c_str() ) );
					element->LinkEndChild( filterElement );
				}
			}
		}

		if ( incGroup->FirstChildElement() != NULL ) { parent->LinkEndChild( incGroup ); }
		else { delete incGroup; }

		if ( srcGroup->FirstChildElement() != NULL ) { parent->LinkEndChild( srcGroup ); }
		else { delete srcGroup; }

		if ( noneGroup->FirstChildElement() != NULL ) { parent->LinkEndChild( noneGroup ); }
		else { delete noneGroup; }
	}

	void VS2010Generator::GenerateProjectFilterItems( TiXmlElement* parent, IdegenProject* project )
	{
		TiXmlElement* group = new TiXmlElement( "ItemGroup" );
		
		const IdegenSourceSetList sources = project->GetSources();
		for ( IdegenSourceSetList::const_iterator it = sources.begin(); it != sources.end(); ++it )
		{
			const ProjectFilterList filters = (*it)->GetFilters();
			for ( ProjectFilterList::const_iterator filterIt = filters.begin(); filterIt != filters.end(); ++filterIt )
			{
				WriteProjectFilter( group, *filterIt );
			}
		}

		if ( group->FirstChildElement() != NULL )
		{
			parent->LinkEndChild( group );
		}
		else
		{
			delete group;
		}
	}

	void VS2010Generator::GenerateSolution( Settings* settings, IdegenGenerator* generator )
	{
		settings;
		const std::string solutionPath = m_OutputPath;
		const std::string solutionFile = solutionPath + "\\" + generator->GetName() + ".sln";
		std::wofstream os( solutionFile );
		
		wchar_t bomMarker[ 4 ];

		bomMarker[0] = (wchar_t)0xEF;
		bomMarker[1] = (wchar_t)0xBB;
		bomMarker[2] = (wchar_t)0xBF;
		bomMarker[3] = (wchar_t)0x00;

		os << bomMarker << std::endl;

		WriteSolutionVersion( os );
		
		const IdegenProjectList projects = generator->GetProjects();
		for ( IdegenProjectList::const_iterator it = projects.begin(); it != projects.end(); ++it )
		{
			const IdegenProject* project = (*it);
			const std::string projectName = project->GetName();
			const std::wstring convertedName = std::wstring( projectName.begin(), projectName.end() );
			
			const std::string outputPath = AbsoluteToRelativePath( m_OutputPath, solutionPath ) + project->GetName() + ".vcxproj";
			const std::wstring finalPath = std::wstring( outputPath.begin(), outputPath.end() );

			os << L"Project(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"";
			os << convertedName;
			os << "\", \"";
			os << finalPath;
			os << "\", \"{A47B2FC6-FF7F-4958-8EB5-547ED94D4E6D}\"" << std::endl;
			os << L"EndProject" << std::endl;
		}
		
		os << L"Global" << std::endl;
		os << L"EndGlobal" << std::endl;

		os.close();
	}

	void VS2010Generator::WriteDefinitions( TiXmlElement* element, IdegenProject* project, const IdegenProjectConfig* config )
	{
		const DefinitionList definitions = config->GetDefinitions();

		std::string platform = config->GetPlatform();
		std::transform( platform.begin(), platform.end(), platform.begin(), ::tolower );

		std::string result;
		for ( size_t i = 0; i < definitions.size(); ++i )
		{
			result += definitions[ i ] + ";";
		}

		result += config->UseDebugLibraries() ? "_DEBUG;" : "NDEBUG;";

		if ( platform == "win32" )
		{
			result += "WIN32;";
		}

		if ( project->GetType() == IdegenProjectTypeStaticLibrary )
		{
			result += "_LIB;";
		}

		result += "%(PreprocessorDefinitions)";	
		WriteElementValue( element, "PreprocessorDefinitions", result.c_str() );
	}

	void VS2010Generator::WriteIncludeDirectories( TiXmlElement* element, Settings* settings, const IdegenProjectConfig* config )
	{
		std::string result;

		const IncludePathList paths = config->GetIncludeDirectories();
		for ( IncludePathList::const_iterator it = paths.begin(); it != paths.end(); ++it )
		{
			result += RelativeToAbsolutePath( *it, settings->GetBasePath(), true ) + ";";
		}

		WriteElementValue( element, "AdditionalIncludeDirectories", result.c_str() );
	}

	void VS2010Generator::WriteLibraryDirectories( TiXmlElement* element, Settings* settings, const IdegenProjectConfig* config )
	{
		std::string result;

		const LibraryPathList paths = config->GetLibraryDirectories();
		for ( LibraryPathList::const_iterator it = paths.begin(); it != paths.end(); ++it )
		{
			result += RelativeToAbsolutePath( *it, settings->GetBasePath(), true ) + ";";
		}
		
		WriteElementValue( element, "AdditionalLibraryDirectories", result.c_str() );
	}

	TiXmlElement* VS2010Generator::WriteProjectConfiguration( const std::string& config, const std::string& platform )
	{
		TiXmlElement* parent = new TiXmlElement( "ProjectConfiguration" );
		parent->SetAttribute( "Include", (config + "|" + platform).c_str() );

		TiXmlElement* configElement = new TiXmlElement( "Configuration" );
		configElement->LinkEndChild( new TiXmlText( config.c_str() ) );
		parent->LinkEndChild( configElement );

		TiXmlElement* platformElement = new TiXmlElement( "Platform" );
		platformElement->LinkEndChild( new TiXmlText( platform.c_str() ) );
		parent->LinkEndChild( platformElement );

		return parent;
	}

	void VS2010Generator::WriteProjectFilter( TiXmlElement* parent, IdegenProjectFilter* filter )
	{
		TiXmlElement* element = new TiXmlElement( "Filter" );
		element->SetAttribute( "Include", filter->GetFullName().c_str() );
		parent->LinkEndChild( element );

		TiXmlElement* uidElement = new TiXmlElement( "UniqueIdentifier" );
		uidElement->LinkEndChild( new TiXmlText( "{b63c1dca-65e3-43cb-ae51-01e76fa15efb}" ) );
		element->LinkEndChild( uidElement );

		const ProjectFilterList filters = filter->GetFilters();
		for ( ProjectFilterList::const_iterator it = filters.begin(); it != filters.end(); ++it )
		{
			WriteProjectFilter( parent, *it );
		}
	}

	void VS2010Generator::WriteSolutionVersion( std::wostream& os )
	{
		os << "Microsoft Visual Studio Solution File, Format Version 11.00" << std::endl;
		os << "# Visual Studio 2010" << std::endl;
	}
}
